package com.julius.design.thread.pool;

import java.util.List;
import java.util.concurrent.*;

/**
 * @author Administrator
 * @date 2021-06-17 下午 7:19
 * <p>
 *     主要用于学习类 {@link java.util.concurrent.ThreadPoolExecutor}的相关源码知识
 * </p>
 */
public class ThreadPoolExcetorTest {

    /**
     * 创建一个线程池，一共有7个参数
     * <ol>
     *     <li>核心线程数</li>
     *     <li>最大线程数</li>
     *     <li>线程保活时间</li>
     *     <li>线程保活时间单位</li>
     *     <li>阻塞队列(BlockingQueue)</li>
     *          <li>有界阻塞队列 ArrayBlockingQueue</li>
     *          <li>无界阻塞队列 LinkedBlockingQueue</li>
     *          <li>同步队列，容量为0 SynchronousQueue</li>
     *     <li>线程工厂（ThreadFactory）</li>
     * </ol>
     */
    ThreadPoolExecutor executor = new ThreadPoolExecutor(10,15,10,
            TimeUnit.SECONDS, new ArrayBlockingQueue<>(1<<4), Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());


    /**
     * 问题1：
     *  当corePoolSize = 0时会发生什么？？
     * 答案：
     *  上述问题需区分JDK版本。在1.6版本之后，如果corePoolSize=0，提交任务时如果线程池为空，则会立即创建一个线程来执行任务（先排队再获取）；
     *  如果提交任务的时候，线程池不为空，则先在等待队列中排队，只有队列满了才会创建新线程。
     *  所以，优化在于，在队列没有满的这段时间内，会有一个线程在消费提交的任务；1.6之前的实现是，必须等队列满了之后，才开始消费。
     */
    public static void questionOne(){
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(0, 5, 10, TimeUnit.MINUTES, new LinkedBlockingQueue<>(1 << 4),Executors.defaultThreadFactory());
        for (int i = 0;i<100;i++){
            int finalI = i;
            threadPoolExecutor.execute(()->System.out.println(Thread.currentThread().getName()+"----"+ finalI));
            /**
             * 我们来看下execute方法的执行步骤，按照源码中的描述来看。主要分为三步
             *  第一步：
             *      如果正在运行的线程小于核心线程数，尝试用给定的命令作为线程池的第一个任务启动一个新线程
             *      调用addWorker自动检查runState和workerCount,并且通过返回false防止在出现警告是去增加线程
             *  第二步：
             *      如果任务能够成功的添加到队列，我们仍然需要去再次检查是否应该添加一个线程（因为现有的线程可能再上次检查时已经死亡）
             *      或者线程池在进入这个方法后关闭了。因此我们要重新检查状态，如果有必要，如果停止排队，则回滚队列，如果没有排队，开启一个线程
             *  第三步：
             *      如果我们不能将任务添加到队列，我们尝试去添加一个新的线程，如果失败了，我们知道我们被关闭或者饱和了，所以拒绝这个任务
             *
             *  runState:
             *      指定线程的状态是运行中或者停止
             *      indicating whether running, shutting down etc
             *  workerCount:
             *      指定线程的有效数量
             *      indicating the effective number of threads
             */
            /*
             * Proceed in 3 steps:
             *
             * 1. If fewer than corePoolSize threads are running, try to
             * start a new thread with the given command as its first
             * task.  The call to addWorker atomically checks runState and
             * workerCount, and so prevents false alarms that would add
             * threads when it shouldn't, by returning false.
             *
             * 2. If a task can be successfully queued, then we still need
             * to double-check whether we should have added a thread
             * (because existing ones died since last checking) or that
             * the pool shut down since entry into this method. So we
             * recheck state and if necessary roll back the enqueuing if
             * stopped, or start a new thread if there are none.
             *
             * 3. If we cannot queue task, then we try to add a new
             * thread.  If it fails, we know we are shut down or saturated
             * and so reject the task.
             */
        }
    }


    /**
     *  问题2：
     *       线程池创建后，会立刻创建核心线程吗？
     *  答案：
     *       一般情况下是不会启动核心线程的，只有在有任务提交时才会启动核心线程
     *       除非调用了prestartCoreThread/prestartAllCoreThreads来预先启动核心线程
     *       prestartCoreThread：
     *          开启一个核心线程，使其无所事事地等待工作。这将覆盖仅在执行新任务时才启动核心线程的默认策略
     *          Starts a core thread, causing it to idly wait for work. This overrides the default policy of starting core threads only when new tasks are executed.
     *       prestartAllCoreThreads：
     *          开启所有核心线程
     *          Starts all core threads.
     */
    public static void questionTwo(){
        ThreadPoolExecutor executor = new ThreadPoolExecutor(2,10,30,TimeUnit.SECONDS,new LinkedBlockingQueue<>(1<<4));
        executor.prestartCoreThread();
        int i = executor.prestartAllCoreThreads();
        System.out.println(executor.getActiveCount());
        executor.execute(()-> System.out.println(111));
        System.out.println(executor.getActiveCount());
        executor.shutdown();
    }

    /**
     * 问题3：
     *      核心线程永远不会被销毁吗？
     * 答案：
     *      首先解析下这个问题，一般来说并没有给哪些线程打上core的标签，应该是在谈论闲置线程（这些线程的数量是核心线程数）终结策略的问题
     *      在JDK1.6之前是尽量保持核心线程数（corePoolSize）数量的线程，但是在之后确对其进行了优化，使用一个参数去控制这个核心线程的保持机制
     *      这个参数就是allowsCoreThreadTimeOut（默认值为false）,如果该参数为true，则允许核心线程数超时时间后终结
     *
     *
     * 请注意这种策略和corePoolSize=0的区别。我总结的区别是：
     *      corePoolSize=0：在一般情况下只使用一个线程消费任务，只有当并发请求特别多、等待队列都满了之后，才开始用多线程。
     *      allowsCoreThreadTimeOut=true && corePoolSize>1：在一般情况下就开始使用多线程（corePoolSize个），当并发请求特别多，等待队列都满了之后，继续加大线程数。
     *      但是当请求没有的时候，允许核心线程也终止。
     *      所以corePoolSize=0的效果，基本等同于allowsCoreThreadTimeOut=true && corePoolSize=1，但实现细节其实不同。
     */
    public static void questionThree(){
        ThreadPoolExecutor executor = new ThreadPoolExecutor(2,1<<4,10,TimeUnit.SECONDS,new LinkedBlockingQueue<>(1<<4));
//        System.out.println(executor.allowsCoreThreadTimeOut());
        executor.allowCoreThreadTimeOut(false);
//        System.out.println(executor.allowsCoreThreadTimeOut());
        for (int i = 0;i<100;i++){
            int finalI = i;
            executor.execute(()-> System.out.println(Thread.currentThread().getName()+"---->"+ finalI));
        }
        executor.prestartAllCoreThreads();
        System.out.println(executor.getActiveCount());
        System.out.println(executor.getActiveCount());
        System.out.println(executor.getActiveCount());
        System.out.println(executor.getActiveCount());
        System.out.println(executor.getActiveCount());
        try {
            Thread.sleep(10*1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(executor.getActiveCount());
    }

    /**
     * 问题4：
     *      如何保证线程不被销毁？
     * 答案：
     *  实现方式非常巧妙，核心线程（Worker）即使一直空闲也不终止，是通过workQueue.take()实现的，它会一直阻塞到从等待队列中取到新的任务。
     *  非核心线程空闲指定时间后终止是通过workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS)实现的，
     *  一个空闲的Worker只等待keepAliveTime，如果还没有取到任务则循环终止，线程也就运行结束了。
     *
     *  引申思考：
     *      Worker本身就是个线程，它再调用我们传入的Runnable.run()，会启动一个子线程么？如果你还没有答案，再回想一下Runnable和Thread的关系。
     *      Runnable接口定义说是这只是个任务，其实要Thread.start才会开启一个线程，所以runWorker()中调用队列中的task的run()并不会开启一个子线程
     */
    public static void questionFour(){
    }

    /**
     * 问题5：
     *      空闲线程过多会有什么问题？
     * 答案：
     *      线程池保持空闲的核心线程是默认配置，但是占用内存较小，不会有一些问题。但是如果业务代码中缓存的ThreadLocal缓存的数据过多，又不清理就会出现问题
     *      如果你的应用线程数处于高位，那么需要观察一下YoungGC的情况，估算一下Eden大小是否足够。如果不够的话，可能要谨慎地创建新线程，并且让空闲的线程终止；必要的时候，可能需要对JVM进行调参
     */
    public static void questionFive(){

    }

    /**
     * 问题6：
     *      keepAliveTime=0时会怎样？
     * 答案：
     *      在JDK1.8中。当keepAliveTIme=0时，标识核心线程在使用完之后会立即终止
     *      默认情况下，keepAliveTime小于0，初始化的时候才会报错；但如果allowsCoreThreadTimeOut，keepAliveTime必须大于0，不然初始化报错
     */
    public static void questionSix(){

    }

    /**
     * 问题7：
     *      线程池需要关闭吗？
     * 答案：
     *      一般来说，线程池的生命周期是跟着服务的生命周期的，如果服务停止了，那么需要shutdown方法进行停止线程池，
     *      所以ExecutorService.shutdown()方法一般封装在服务的shutdown方法中，但是一般不需要去手动关闭
     */
    public static void questionSeven(){

    }

    /**
     * 问题8：
     *      shutdown()和shutdownNow()的区别？
     * 答案：
     *      shutdown： 平缓关闭，等到放入队列中的任务全部执行在结束。
     *      shutdownNow: 立刻关闭，停止正在执行的任务，并返回队列中未执行的任务。
     */
    public static void questionEight(){
        ThreadPoolExecutor executor = new ThreadPoolExecutor(10,10,10,TimeUnit.SECONDS,new LinkedBlockingQueue<>(1<<14));
        for (int i = 0; i < 100; i++) {
            int finalI = i;
            executor.execute(()->{
                System.out.println("当前任务:---"+ finalI);
            });
        }
        //平缓关闭，等到放入队列中的任务全部执行在结束
//        executor.shutdown();

        // 立刻关闭，停止正在执行的任务，并返回队列中未执行的任务。
        List<Runnable> list = executor.shutdownNow();
        System.out.println(list.size());
    }

    /**
     * 问题9：
     *      如何进行异常处理？
     * 答案：
     *
     */
    public static void questionNine(){

    }



    public static void main(String[] args) {
//        questionOne();
//        questionTwo();
//        questionThree();
        questionEight();
    }


}
